#include "ET++.ph"
#ifdef __GNUG__
#pragma implementation
#endif

#include "Scroller.h"

#include "Math.h"
#include "Class.h"
#include "ScrollBar.h"
#include "Clipper.h"
#include "View.h"
#include "Look.h"
#include "SeqColl.h"

#include "Manager.h"

int HasFocus= BIT(eCompVObjLast+1);

//---- Scroller ----------------------------------------------------------------

NewMetaImpl(Scroller,CompositeVObject, (TAP(sb), TP(inner), TP(vf), TP(vop),
				TE(controls), TE(visibleControls), T(ms)));

Scroller::Scroller(VObject *v, const Point &e, int id, ScrollDir w)
			: ms(e), CompositeVObject(id, (SeqCollection*) 0)
{
	controls= w;
	vop= v;
	visibleControls= eScrollNone;
}

Scroller::Scroller(VObject *v, Ink *bg, const Point &e, int id, ScrollDir w)
			: ms(e), CompositeVObject(id, (SeqCollection*) 0)
{
	controls= w;
	vop= v;
	visibleControls= eScrollNone;
	SetBgInk(bg);
}

Clipper *Scroller::MakeClipper()
{
	return new Clipper(vop, ms, GetId());
}

ScrollBar *Scroller::MakeScrollBar(int id)
{
	return new ScrollBar(cIdNone, (Direction) Math::Even(id));
}

VObject *Scroller::MakeInner()
{
	return vf= MakeClipper();
}

Clipper *Scroller::GetClipper()
{
	if (vf == 0)
		GetInner();
	return vf;
}

ScrollBar *Scroller::GetScrollBar(int i)
{
	if ((sb[i] == 0) && (controls & BIT(i)))
		Add(sb[i]= MakeScrollBar(i));
	return sb[i];
}

VObject *Scroller::GetInner()
{
	if (inner == 0)
		Add(inner= MakeInner());
	return inner;
}

Command *Scroller::Input(Point lp, Token &t, Clipper *cl)
{
	if (t.IsKey() || t.IsFunctionKey() || t.IsCursorKey())
		return GetClipper()->Input(lp, t, cl);
	return CompositeVObject::Input(lp, t, cl);
}

void Scroller::Control(int id, int part, void *val)
{
	switch (part) {
	case cPartScrollPos:
		BubbleUpdate(*((Point*)val));
		break;
		
	case cPartExtentChanged:
	case cPartViewSize:
		ViewSizeChanged();
		break;
		
	case cPartScrollPage:
	case cPartScrollRel:
	case cPartScrollStep:
	case cPartScrollAbs:
	case cPartScrollHAbs:
	case cPartScrollVAbs:
		GetClipper()->Scroll(part, *((Point*)val));
		break;
		
	default:
		CompositeVObject::Control(id, part, val);
		break;
	}
}

void Scroller::BubbleUpdate(const Point &bs)
{
	for (int i= 0; i < 4; i++)
		if (visibleControls & BIT(i))
			GetScrollBar(i)->SetVal(bs);
}

bool Scroller::NeedControls()
{
	bool hbar= FALSE, vbar= FALSE;
	ScrollDir lastVisibleControls= visibleControls;
	Point fgap((int)gLook->FrameLayout()->GetValue(this, 1)),
		border(2 * (int)gLook->FrameLayout()->GetValue(this, 0));
	Point e(GetExtent()-border);
	Point vs(GetClipper()->GetViewSize()+GetClipper()->GetShift());

	visibleControls= (ScrollDir)(controls & ~eScrollHideScrolls);
	if (controls & eScrollHideScrolls) {
		if (vs.x > e.x) {   // we need horizontal scrollbars
			hbar= TRUE;
			e.y= e.y - GetScrollBar(1)->GetMinSize().extent.y - fgap.y;
		}
		if (vs.y > e.y) {   // we need vertical scrollbars
			vbar= TRUE;
			e.x= e.x - GetScrollBar(0)->GetMinSize().extent.x - fgap.x;
		}
		if (vs.x > e.x) {   // we need horizontal scrollbars
			hbar= TRUE;
		}
		if (vs.y > e.y) {   // we need vertical scrollbars
			vbar= TRUE;
		}
		if (!hbar)    // we don't need horizontal scrollbars
			visibleControls= (ScrollDir)(visibleControls&~(eScrollUp | eScrollDown));
		if (!vbar)    // we don't need vertical scrollbars
			visibleControls= (ScrollDir)(visibleControls&~(eScrollLeft | eScrollRight));
	}
	return visibleControls != lastVisibleControls;
}

void Scroller::ViewSizeChanged()
{
	if (NeedControls()) {
		SetExtent(GetExtent());
		SetOrigin(GetOrigin());
		ForceRedraw();
	} else {
		Point newviewsize(GetClipper()->GetViewSize());
		for (int i= 0; i < 4; i++)
			if (visibleControls & BIT(i))
				GetScrollBar(i)->SetMax(newviewsize);
	}
}

void Scroller::Draw(Rectangle r)
{
	int code;
	EvtHandler *fh= 0;
	Manager *m= GetManager();
	if (m)
		fh= m->GetFirstHandler();
	if (vop == fh)
		code= 1;
	else
		code= 0;
	CompositeVObject::Draw(r);
	gLook->FrameLayout()->Adorn(this, GetInner()->contentRect, code);
}

void Scroller::SetBgInk(Ink *bg)
{
	GetClipper()->SetBgInk(bg);
}

Ink *Scroller::GetBgInk()
{
	return GetClipper()->GetBgInk();
}

Metric Scroller::GetMinSize()
{
	Point fgap((int)gLook->FrameLayout()->GetValue(this, 1)),
		border(2 * (int)gLook->FrameLayout()->GetValue(this, 0)),
		sbSizes,
		clipperSize(GetInner()->GetMinSize().extent + border);

	for (int i= 0; i < 4; i++) {
		ScrollBar *scb= GetScrollBar(i);
		if (scb) {
			Point ee(scb->GetMinSize().extent + fgap);
			if (Math::Even(i)) {      // right & left scrollbar
				sbSizes.x+= ee.x;
				clipperSize.y= Math::Max(clipperSize.y, ee.y);
			} else {            // up & down scrollbar
				sbSizes.y+= ee.y;
				clipperSize.x= Math::Max(clipperSize.x, ee.x);
			}
		}
	}
	return clipperSize + sbSizes;
}

void Scroller::SetExtent(Point e)
{
	Point fgap((int)gLook->FrameLayout()->GetValue(this, 1)),
		border(2 * (int)gLook->FrameLayout()->GetValue(this, 0)),
		viewExtent(GetClipper()->GetViewSize());

	VObject::SetExtent(e);

	NeedControls();

	if (visibleControls & eScrollRight)
		e.x= e.x - GetScrollBar(0)->GetMinSize().extent.x - fgap.x;

	if (visibleControls & eScrollDown)
		e.y= e.y - GetScrollBar(1)->GetMinSize().extent.y - fgap.y;

	if (visibleControls & eScrollLeft)
		e.x= e.x - GetScrollBar(2)->GetMinSize().extent.x - fgap.x;

	if (visibleControls & eScrollUp)
		e.y= e.y - GetScrollBar(3)->GetMinSize().extent.y - fgap.y;

	GetInner()->SetExtent(e-border);
	Point vs(GetClipper()->GetExtent());

	for (int i= 0; i < 4; i++) {
		ScrollBar *scb= GetScrollBar(i);
		if (scb) {
			if (visibleControls & BIT(i)) {
				Point size(scb->GetMinSize().extent);
				if (Math::Even(i))
					size.y= e.y;
				else
					size.x= e.x;
				if (!scb->IsOpen())
					scb->Open(TRUE);
				scb->SetAll(size, vs, viewExtent);
			} else {
				if (scb->IsOpen())
					scb->Open(FALSE);
			}
		}
	}
}

void Scroller::SetOrigin(Point at)
{
	Point fgap((int)gLook->FrameLayout()->GetValue(this, 1)),
		border((int)gLook->FrameLayout()->GetValue(this, 0)),
		left, middle(GetInner()->GetExtent()+2*border);

	VObject::SetOrigin(at);

	if (visibleControls & eScrollLeft)
		left.x= GetScrollBar(2)->Width() + fgap.x;
	if (visibleControls & eScrollUp)
		left.y= GetScrollBar(3)->Height() + fgap.y;
		
	GetInner()->SetOrigin(at+left+border);

	if (visibleControls & eScrollRight)
		GetScrollBar(0)->SetOrigin(at+left+Point(middle.x+fgap.x, 0));
	if (visibleControls & eScrollDown)
		GetScrollBar(1)->SetOrigin(at+left+Point(0, middle.y+fgap.y));
	if (visibleControls & eScrollLeft)
		GetScrollBar(2)->SetOrigin(at+Point(0, left.y));
	if (visibleControls & eScrollUp)
		GetScrollBar(3)->SetOrigin(at+Point(left.x, 0));
}

OStream& Scroller::PrintOn(OStream &s)
{
	VObject::PrintOn(s);
	return s << vop SP << (int)controls SP << ms SP;
}

IStream& Scroller::ReadFrom(IStream &s)
{
	FreeAll();
	sb[0]= sb[1]= sb[2]= sb[3]= 0;
	vf= 0;
	inner= 0;
	visibleControls= eScrollNone;

	VObject::ReadFrom(s);
	return s >> vop >> Enum(controls) >> ms;
}

//---- GridViewClipper ---------------------------------------------------------

class GridViewClipper: public Clipper {
	int cs;
public:
	GridViewClipper(int c, VObject *vp, Point minsize);
	void ConstrainScroll(Point *newOrigin);
};

GridViewClipper::GridViewClipper(int c, VObject *vp, Point minsize)
											: Clipper(vp, minsize, cIdNone, 0)
{
	cs= c;
}

void GridViewClipper::ConstrainScroll(Point *newOrigin)
{
	if (cs & 1)
		newOrigin->x= relOrigin.x;
	if (cs & 2)
		newOrigin->y= relOrigin.y;
}

//---- MarginScroller ----------------------------------------------------------

static bool lock;

NewMetaImpl(MarginScroller,Scroller, (T(margin)));

MarginScroller::MarginScroller(VObject *v, const Point &m, const Point &e)
												: margin(m), Scroller(v, e)
{
}

Point MarginScroller::DoCalcMargin()
{
	return margin;
}

VObject *MarginScroller::MakeInner()
{
	Point c(DoCalcMargin());

	if (c.x == 0 && c.y == 0)
		return Scroller::MakeInner();

	vf= new GridViewClipper(0, vop, ms);
	vf->SetShift(c);

	if (c.x == 0 && c.y != 0) {
		cl[0]= new GridViewClipper(2, vop, Point(ms.x, c.y)),
		cl[0]->SetFlag(eVObjVFixed);
		return
			new VBox(gPoint0,(VObjAlign)(eVObjHExpand|eVObjVExpand),
				cl[0],
				vf,
				0
			);
	}
	if (c.x != 0 && c.y == 0) {
		cl[0]= new GridViewClipper(1, vop, Point(c.x, ms.y)),
		cl[0]->SetFlag(eVObjHFixed);
		return
			new HBox(gPoint0, (VObjAlign)(eVObjHExpand|eVObjVExpand),
				cl[0],
				vf,
				0
			);
	}

	VObject *v= new GridViewClipper(3, vop, c);
	v->SetFlag(eVObjHFixed|eVObjVFixed);

	cl[0]= new GridViewClipper(2, vop, Point(ms.x, c.y)),
	cl[0]->SetFlag(eVObjVFixed);
	cl[0]->SetShift(Point(c.x, 0));

	cl[1]= new GridViewClipper(1, vop, Point(c.x, ms.y)),
	cl[1]->SetFlag(eVObjHFixed);
	cl[1]->SetShift(Point(0, c.y));

	return
		new Box(cIdNone, gPoint2, gPoint0,(VObjAlign)(eVObjHExpand|eVObjVExpand),
			v,
			cl[0],
			cl[1],
			vf,
			0
		);
}

void MarginScroller::Control(int id, int part, void *val)
{
	if (part == cPartScrollPos) {
		if (!lock) {
			Point p(*((Point*)val));
			lock= TRUE;
			BubbleUpdate(p);
			if (cl[0])
				cl[0]->Scroll(cPartScrollAbs, p);
			if (cl[1])
				cl[1]->Scroll(cPartScrollAbs, p);
			GetClipper()->Scroll(cPartScrollAbs, p);
			lock= FALSE;
		}
	} else
		Scroller::Control(id, part, val);
}

